#include "../include/minishellBase.h"


// ============ fuction definition ============

void Print_current_workpath(){
	char cwd[1024];
	if(getcwd(cwd, sizeof(cwd)) != NULL){
		printf("%s ",cwd);
	}else{
		perror("$ Error for getcwd failure\n");
		exit(-1);
	}
}

int initialize(){
	/**
	 * @brief Initialize the program by reading and printing the welcome text.
	 *
	 * This function opens the "welcome_text.sh" file, reads its contents line by line,
	 * and prints each line to the console. It first clears the console using the 
	 * system("clear") command to ensure a clean slate before displaying the welcome text.
	 *
	 * @return int Returns TRUE if the initialization process is successful, FALSE otherwise.
	 */
	FILE * fp = NULL;
	char line_str[LINE_LENGTH];
	char * line_ptr = line_str;

	if((fp = fopen("./docs/welcome_text.sh","r")) != NULL){
        printf("fopen completed.\n");
	}else{
		printf("fopen failed.\n");
		return FALSE;
	}

	system("clear");
	while((line_ptr = fgets(line_str,LINE_NUMBER,fp)) != NULL)
		printf("%s",line_ptr);
	Print_current_workpath(); printf(">> ");
	fclose(fp);

	return TRUE;
}

void Print_errorInfo(const char * errorstr,const char * filename, const int linenum ){
	fprintf(stderr, "$Error @(%s:%d) ", filename, linenum);
	perror(errorstr);
}

void Semantic_Segmentation(char ** token, char ** inputstr, const char * seg_symbol, int iswarning){
	/**
	 * @brief segment the given string by the segmentation symbol and return the desired token as well as the string that has been segmented.
	 * 
	 * @param	inputstr	the pointer to the given string pointer
	 * @param	seg_symbol	the symbol by which the input string to be segmented
	 * @param	iswarning	whether to print a warning message if the segmentation symbol is not found in the input string
	 * 
	 * @return	the pointer to the desired token
	 */
	Clear_space(inputstr,strlen(*inputstr));

	char * needle = NULL; char * buffer = NULL;
	// char * token = (char*)calloc(MAX_PROMPT_LENGTH,sizeof(char));
	
	// char ** token_ptr = &token;
	int token_len = 0;

	if((needle = strstr(*inputstr,seg_symbol)) != NULL){
		if(strncmp(*inputstr,seg_symbol,strlen(seg_symbol)) == 0){ // if the segmentation symbol occurred at the beginning
			if((buffer = strstr((*inputstr)+1,seg_symbol))!= NULL){
				// extract the parameters seperated by segmentation character.
				token_len = buffer - *inputstr + 1;

				// allocate memory space for the token
				// token = (char*)realloc(token, (token_len+1)*sizeof(char));
				strncpy(* token,*inputstr, token_len);
				Clear_space(token,strlen(* token));

				*inputstr += token_len;
				Clear_space(inputstr,strlen(*inputstr));
			}else{
				// free(token); token = NULL;
				*inputstr = *inputstr;
			}
			
			// return token;
		}else{
			// extract the parameters seperated by segmentation character.
			token_len = needle - *inputstr;

			// allocate memory space for the token
			// token = (char*)realloc(token, (token_len+1)*sizeof(char));
			// token = (char*)calloc(token_len+1,sizeof(char));
			strncpy(* token,*inputstr, token_len);
			Clear_space(token,strlen(* token));

			*inputstr += token_len;
			Clear_space(inputstr,strlen(*inputstr));

			// return token;

			// free(token); token = NULL;
		}
	}else{
		char * error_str = (char *) calloc(100,sizeof(char));
		sprintf(error_str,"No such segmentation character in the given string as \"%s\"",*inputstr);
		 
		if(iswarning) Print_errorInfo(error_str,__FILE__,__LINE__);

		free(error_str); error_str = NULL;
		// free(token); token = NULL;
		strcpy(*token,"");
	}
}

int Identify_promptType(char* str, PROMPT_TYPE* array_selfDef_type, size_t array_len,PARSER *cmd){
	/**
	 * @brief	Identify the type of prompt input by the user, based on the structural array of self-defined types.
	 *
	 * This function takes a string input from the user and compares it with a predefined array of prompt types.
	 * It aims to identify the specific type of prompt the user has entered.
	 *
	 * @param	str					A pointer to the string of prompt input from the user.
	 * @param	array_selfDef_type	A pointer to the structural array containing self-defined prompt types.
	 * @param	array_len			The length of the structural array of self-defined prompt types.
	 * @param	cmd					A pointer to the PARSER structure that will hold the identified command details.
	 *
	 * @return	int					Returns TRUE (1) if the prompt type is successfully identified,
	 * 								and FALSE (0) if no matching prompt type is found.
	 *
	 * Note: The function also populates the PARSER structure with details of the identified command,
	 * such as the command text, index within the self-defined array, and a pointer to the specific type.
	 */
    char ** str_ptr = &str;
	Clear_space(str_ptr,strlen(str));

	for(int i = 0; i < (int)array_len; i++){
		if(strlen(*str_ptr) < array_selfDef_type[i].cmd_length) continue;

		if(strncmp(str, array_selfDef_type[i].cmd_text, array_selfDef_type[i].cmd_length) == 0){
			cmd->cmd_string = (char *) calloc(strlen(*str_ptr)+1, sizeof(char));
			strcpy(cmd->cmd_string, *str_ptr);
			cmd->index = i;
			cmd->selfDef_type = array_selfDef_type + i;
			return TRUE;
		}
	}

	// Print_errorInfo("$ Error in Identify_promptType:\n\tNo such prompt type.\n",__FILE__,__LINE__);
	
	return FALSE;
}

void Clear_space(char **str_prt, size_t count){
    /** 
     * @brief   Removes trailing spaces and heading spaces from a given string and updates its length
     *
     * This function is designed to eliminate any trailing and heading whitespace characters from a provided string.
     * It accomplishes this by iterating from the end of the string, replacing any space characters 
     * with null terminators ('\0') until a non-space character is encountered. Additionally, it adjusts
     * the string pointer and length to exclude any leading spaces.
     *
     * @param   str_prt   Pointer to the pointer of the string to be modified
     * @param   count     The current length of the string (including trailing spaces)
    */
	for(; isspace((*str_prt)[count-1]); count--)
		(*str_prt)[count-1] = NULL_CHAR;
	
	for(; isspace(**str_prt); count--)
		*str_prt = (*str_prt + 1);
}

void Free_parser(PARSER **cmd){
	/** 
		* @brief	Free the memory allocated for PARSER structure
		* @param cmd pointer to the pointer of the PARSER structure
		**/
	if(*cmd != NULL){
		if((*cmd)->parameters != NULL){
			for(int i = 0; i < (int)(*cmd)->para_num; i++)
				if(((*cmd)->parameters)[i] != NULL){
					free(((*cmd)->parameters)[i]);
				}
			free((*cmd)->parameters);
		}

		if((*cmd)->cmd_string != NULL) free((*cmd)->cmd_string);

		if((*cmd)->selfDef_type != NULL) (*cmd)->selfDef_type = NULL;
		free((*cmd)); (*cmd) = NULL;
	}
}

void Free_promptTypeList(PROMPT_TYPE ** array_selfDef_type, size_t array_len) {
	/** 
		* @brief	Free the memory allocated for PROMPT_TYPE structure
		* @param array_selfDef_type pointer to the pointer of PROMPT_TYPE structure
		* @param array_len length of the PROMPT_TYPE structure
		**/
    if ((*array_selfDef_type) == NULL) return;

    for (size_t i = 0; i < array_len; i++) {
        // free cmd_text
        if (((*array_selfDef_type) + i)->cmd_text != NULL) {
            free(((*array_selfDef_type) + i)->cmd_text);
            ((*array_selfDef_type) + i)->cmd_text = NULL;
        }

		// free fcn_ptr
        if (((*array_selfDef_type) + i)->fcn_ptr != NULL){
            ((*array_selfDef_type) + i)->fcn_ptr = NULL;
        }
    }

	free((*array_selfDef_type)); (*array_selfDef_type) = NULL;
}

char * Input_string(){
	char* input_str = (char*)calloc(MAX_PROMPT_LENGTH,sizeof(char));
	fgets(input_str,MAX_PROMPT_LENGTH,stdin);
	size_t str_len = strlen(input_str);

	// clear the tailing '\n' char
	if(strncmp(input_str+str_len-1,"\n",1)==0) input_str[str_len-1] = NULL_CHAR;
	if((input_str = (char *)realloc(input_str,str_len*sizeof(char))) == NULL){
		perror("$ Error for realloc failure");
		free(input_str);
		return NULL;
	}else{
		return input_str;
	}
}

char* get_self_path() {
	/**
	 * @brief Get the path of the current executable file
	 * 
	 * This function retrieves the absolute path of the currently executing program.
	 * It uses the readlink function to read the symbolic link /proc/self/exe, which points
	 * to the executable file of the current process.
	 * 
	 * @return char* A pointer to a string containing the absolute path of the executable file.
	 */
    static char path[MAX_ENV_VALUE_LENGTH];
    ssize_t len = readlink("/proc/self/exe", path, sizeof(path)-1);
    if (len == -1) return NULL;
    path[len] = '\0';
    return path;
}

void executeExternCMD(char *input_str, char * path2exe){
	/**
	 * @brief Execute the external command
	 * 
	 * This function executes the external command using the execv function.
	 * It first checks if the command is from the external directory (extern/bin) or the user's bin directory,
	 * and then executes it accordingly.
	 */

	 // Parser the input string
	 char * externCMDtoken = (char *) calloc(MAX_PROMPT_LENGTH,sizeof(char));
	 char ** externCMDargv = (char **) calloc(MAX_PAPA_NUM,sizeof(char *));
	 int i = 1;
	 char * tokenbuf = (char *) calloc(MAX_PROMPT_LENGTH,sizeof(char));
	 strncpy(tokenbuf,"",MAX_PROMPT_LENGTH); // initialize the token string

	 strncpy(externCMDtoken,"",MAX_PROMPT_LENGTH); // initialize the token string

	 char * strbuf = (char *)calloc(strlen(input_str) + 2,sizeof(char));
	 char * const header2strbuf = strbuf;
	 strncpy(strbuf,input_str,strlen(input_str));

	 Semantic_Segmentation(&externCMDtoken,&strbuf," ",false);

	 if(strcmp(externCMDtoken,"") == 0){
		 strncpy(externCMDtoken,input_str,strlen(input_str));

	 }else{
		 // external command token as the first parameter of argv
		 externCMDargv[0] = (char *)calloc(strlen(externCMDtoken)+1,sizeof(char)); 
		 strncpy(externCMDargv[0],"",strlen(externCMDargv[0])); // initialize the token string
		 strncpy(externCMDargv[0],externCMDtoken,strlen(externCMDtoken));
		 
		 do{
			 Semantic_Segmentation(&tokenbuf,&strbuf," ",false);
			 if(strcmp(tokenbuf,"") != 0){
				 externCMDargv[i] = (char *)calloc(strlen(tokenbuf)+1,sizeof(char));
				 strncpy(externCMDargv[i],"",strlen(externCMDargv[i]));
				 strncpy(externCMDargv[i],tokenbuf,strlen(tokenbuf));
				 i += 1;
			 }
		 }while(strcmp(tokenbuf,"") != 0);

		 externCMDargv[i] = (char *)calloc(strlen(strbuf)+1,sizeof(char));
		 strncpy(externCMDargv[i],"",strlen(externCMDargv[i]));
		 strncpy(externCMDargv[i],strbuf,strlen(strbuf));
		 externCMDargv[i+1] = NULL;
	 }
	 
	 // path to the external command's executable file from the repo's directory
	 char * path2externalCMD = (char *)calloc(strlen(path2exe) + strlen("exter/bin") + 2 + strlen(externCMDtoken),sizeof(char));
	 // path to the external command's executable file from user's bin/ directory
	 char * path2usrCMD = (char *)calloc(strlen("/usr/bin/") + 2 + strlen(externCMDtoken),sizeof(char));

	 strncpy(path2externalCMD,path2exe,strlen(path2exe) - strlen("minishell"));
	 strncat(path2externalCMD,"extern/bin/",15);
	 strncat(path2externalCMD,externCMDtoken,strlen(externCMDtoken));

	 strncpy(path2usrCMD,"/usr/bin/",strlen("/usr/bin/") + 1);
	 strncat(path2usrCMD,externCMDtoken,strlen(externCMDtoken));

	 if(access(path2externalCMD, X_OK) == -1){
		 if(access(path2usrCMD, X_OK) == -1){
			 Print_errorInfo("$ Error:\n\tCommand not found\n",__FILE__,__LINE__);
		 }else if(access(path2usrCMD, X_OK) == 0){
			 pid_t pid = fork();
			 if(pid<0){
				 Print_errorInfo("$ Error:\n\tFork failed\n",__FILE__,__LINE__);
			 }else if (pid == 0){
				 execv(path2usrCMD,externCMDargv);
			 
			 }else{
				 // int wstatus = 0;
				 waitpid(-1,NULL,0);
			 }
		 }
		 
	 }else if(access(path2externalCMD, X_OK) == 0){
		 pid_t pid = fork();
		 if(pid<0){
			 Print_errorInfo("$ Error:\n\tFork failed\n",__FILE__,__LINE__);
		 }else if (pid == 0){
			 execv(path2externalCMD,externCMDargv);
			 
		 }else{
			 // int wstatus = 0;
			 waitpid(-1,NULL,0);
		 }
	 }

	 for(int j = 0; j< i+2; j++){
		 free(externCMDargv[j]); externCMDargv[j] = NULL;
	 }
	 free(externCMDargv); externCMDargv = NULL;
	 free(tokenbuf); tokenbuf = NULL;
	 strbuf = header2strbuf;
	 free(strbuf); strbuf = NULL; 

	 free(externCMDtoken); externCMDtoken = NULL;
	 free(path2externalCMD); path2externalCMD = NULL;
	 free(path2usrCMD); path2usrCMD = NULL;
	 // free(path2exe); path2exe = NULL;
}